import { expect } from 'chai';
import * as sinon from 'sinon';
import { checkStackExist } from '../src/checkStackExist';
import * as step from '@flow-step/step-toolkit';
import { ListStacksRequest, ListStacksResponse } from '@alicloud/ros20190910';

describe('checkStackExist', () => {
  let sandbox: sinon.SinonSandbox;
  let mockRos: any;
  let mockParams: any;
  let stepInfoStub: sinon.SinonStub;
  let stepErrorStub: sinon.SinonStub;

  beforeEach(() => {
    sandbox = sinon.createSandbox();
    mockRos = {
      listStacks: sandbox.stub()
    };
    mockParams = {
      STACK_NAME: 'test-stack',
      REGION_ID: 'cn-hangzhou',
      pipelineID: 123,
      pipelineName: 'test-pipeline',
      workSpace: '/test/workspace',
      projectDir: '/test/project',
      buildJobID: 456,
      ROS_DEPLOYMENT_LOCATION: 'cn',
      RAM_ROLE_NAME: 'test-role',
      ROS_STACK_FILE_PATH: 'template.json',
      TIMEOUT_MINUTES: 60
    };
    stepInfoStub = sandbox.stub(step, 'info');
    stepErrorStub = sandbox.stub(step, 'error');
  });

  afterEach(() => {
    sandbox.restore();
  });

  it('should return stackId when stack exists', async () => {
    const mockResponse = {
      body: {
        stacks: [
          {
            stackId: 'stack-12345',
            stackName: 'test-stack'
          }
        ]
      }
    };
    mockRos.listStacks.resolves(mockResponse);

    const result = await checkStackExist(mockParams, mockRos);

    expect(result).to.equal('stack-12345');
    expect(mockRos.listStacks.calledOnce).to.be.true;
    expect(stepInfoStub.calledWith('test-stack exists. About to start updating stacks.')).to.be.true;
  });

  it('should return false when stack does not exist', async () => {
    const mockResponse = {
      body: {
        stacks: []
      }
    };
    mockRos.listStacks.resolves(mockResponse);

    const result = await checkStackExist(mockParams, mockRos);

    expect(result).to.be.false;
    expect(stepInfoStub.calledWith('test-stack does not exist. About to start creating stacks.')).to.be.true;
  });

  it('should return false when stacks is undefined', async () => {
    const mockResponse = {
      body: {}
    };
    mockRos.listStacks.resolves(mockResponse);

    const result = await checkStackExist(mockParams, mockRos);

    expect(result).to.be.false;
    expect(stepInfoStub.calledWith('test-stack does not exist. About to start creating stacks.')).to.be.true;
  });

  it('should handle API errors gracefully', async () => {
    const mockError = {
      code: 'InvalidParameter',
      data: {
        Message: 'Invalid region ID',
        RequestId: 'req-123'
      }
    };
    mockRos.listStacks.rejects(mockError);

    const result = await checkStackExist(mockParams, mockRos);

    expect(result).to.be.false;
    expect(stepErrorStub.calledWith('Failed to listStacks. Code: InvalidParameter, Message: Invalid region ID, RequestId: req-123')).to.be.true;
  });

  it('should handle unexpected errors without message', async () => {
    const mockError = {
      code: 'UnknownError'
    };
    mockRos.listStacks.rejects(mockError);

    const result = await checkStackExist(mockParams, mockRos);

    expect(result).to.be.false;
    expect(stepErrorStub.calledWith('Failed to listStacks. Code: UnknownError, Message: undefined, RequestId: undefined')).to.be.true;
    expect(stepErrorStub.calledWith('Unexpected listStacks error. Error: {\n  "code": "UnknownError"\n}')).to.be.true;
  });

  it('should construct correct ListStacksRequest', async () => {
    const mockResponse = {
      body: {
        stacks: []
      }
    };
    mockRos.listStacks.resolves(mockResponse);

    await checkStackExist(mockParams, mockRos);

    const expectedRequest = new ListStacksRequest({
      regionId: 'cn-hangzhou',
      stackName: ['test-stack'],
      pageSize: 1,
      pageNumber: 1
    });
    
    expect(mockRos.listStacks.calledWith(sinon.match.instanceOf(ListStacksRequest))).to.be.true;
    const actualCall = mockRos.listStacks.getCall(0);
    expect(actualCall.args[0].regionId).to.equal('cn-hangzhou');
    expect(actualCall.args[0].stackName).to.deep.equal(['test-stack']);
  });
});